#pragma once
#include "room.hpp"
#include "matcher.hpp"
#include "onlinemanager.hpp"
#include "log.hpp"
#include "session.hpp"
#include "util.hpp"

#define HOST "127.0.0.1"
#define USRNAME "root"
#define PASSWORD "zpk20020629"
#define DBNAME "gobang"
#define PORT 3306

#define WEBROOT "./wwwroot/"

typedef websocketpp::server<websocketpp::config::asio> wsvr_t;

using namespace std;

// 将各个模块整合起来编写一个ws服务器
class gobang_server
{
private:
	// 将所有的模块对象都放进来
	wsvr_t _wssver;
	string _webroot; // 静态资源路径
	user_table _ust;
	online_manager _om;
	room_manager _rm;
	session_manager _sm;
	matcher _mc;

	// WS服务器回调函数
private:
	// 客户端申请静态资源时的处理函数
	void file_handler(wsvr_t::connection_ptr conn)
	{
		websocketpp::http::parser::request req = conn->get_request();
		string method = req.get_method();
		string uri = req.get_uri();
		string body;
		string realpath = _webroot + uri;
		if (realpath.back() == '/')
			realpath += "login.html";

		// 如果文件不存在，返回404
		bool ret = File_util ::read(realpath, body);
		if (ret == false)
		{
			body += "<html>";
			body += "<head>";
			body += "<meta charset='UTF-8'/>";
			body += "</head>";
			body += "<body>";
			body += "<h1> Not Found </h1>";
			body += "</body>";
			conn->set_status(websocketpp::http::status_code::not_found);
			conn->set_body(body);
			return;
		}
		conn->set_status(websocketpp::http::status_code::ok);
		conn->set_body(body);
		return;
	}
	// 专门填充对应响应的函数
	void http_response(websocketpp::http::status_code::value code,
					   bool result, const string reason, wsvr_t::connection_ptr &conn)
	{
		Json::Value resp;

		resp["result"] = result;
		resp["reason"] = reason;

		string body;
		Json_util::serialize(resp, body);

		// 设置响应状态码，对应的"Content-Type"，以及报文
		conn->set_status(code);
		conn->append_header("Content-Type", "application/json");
		conn->set_body(body);

		return;
	}

	// 注册时调用的函数
	void register_handler(wsvr_t::connection_ptr conn)
	{
		// 获取注册的报文中的内容，然后将其交付予数据库进行查验比较，如果插入失败了那么就是当前用户已注册过，或者是重名了
		websocketpp::http::parser::request req = conn->get_request();
		string body = req.get_body();
		Json::Value user;
		// 序列化后还需要对返回值做判断
		bool Json_ret = Json_util::unserialize(body, user);
		if (Json_ret == false)
		{
			DLOG("注册Json反序列化失败！");
			http_response(websocketpp::http::status_code::bad_request, false, "Json反序列化失败！", conn);
			return;
		}
		//  反序列化之后，得到一个Json格式的请求数据，里面包含了用户的ID，以及对应的注册信息
		// 但是用户名或者密码都不能为空
		if (user["username"].isNull() || user["password"].isNull())
		{
			DLOG("注册必须输入用户名以及密码！");
			http_response(websocketpp::http::status_code::bad_request, false, "必须输入用户名以及密码！", conn);
			return;
		}

		// 新增用户
		bool uret = _ust.insert(user);
		if (uret == false)
		{
			DLOG("当前用户名已注册过！");
			http_response(websocketpp::http::status_code::bad_request, false, "当前用户名已注册过！", conn);
			return;
		}

		// 走到这就是成功了
		return http_response(websocketpp::http::status_code::ok, true, "注册成功！", conn);
	}

	// 登录请求调用的函数
	void login_handler(wsvr_t::connection_ptr conn)
	{
		// 基本逻辑同注册，首先我们还是获取对应的正文内容，然后对比对应数据库内部的用户数据是否存在，如果不存在，那么就跳转回注册界面
		websocketpp::http::parser::request req = conn->get_request();
		string body = req.get_body();
		Json::Value user;
		// 序列化后还需要对返回值做判断
		bool Json_ret = Json_util::unserialize(body, user);
		if (Json_ret == false)
		{
			DLOG("登录Json反序列化失败！");
			return http_response(websocketpp::http::status_code::bad_request, false, "Json反序列化失败！", conn);
		}

		// 登录依然需要校验用户名和密码是否缺少
		if (user["username"].isNull() || user["password"].isNull())
		{
			DLOG("登录必须输入用户名以及密码！");
			return http_response(websocketpp::http::status_code::bad_request, false, "必须输入用户名以及密码！", conn);
		}

		// 走到这一步，就需要向数据库内部进行比对，由于注册阶段其实已经保证当前用户会存在，我们要做的就是对比密码是否相等
		if (_ust.login(user) == false)
		{
			DLOG("密码有误");
			return http_response(websocketpp::http::status_code::bad_request, false, "登录失败，密码有误！", conn);
		}

		// 登录成功之后，为其创建session并返回Cookie
		// seesion_ptr create_session(uint64_t ssid, session_statu statu)
		// 用用户的ID创建session
		uint64_t ssid = user["id"].asUInt64();
		seesion_ptr sp = _sm.create_session(ssid, ONLINE);
		if (sp.get() == nullptr)
		{
			DLOG("会话创建失败");
			return http_response(websocketpp::http::status_code::bad_request, false, "会话创建失败", conn);
		}
		// 设置session的生命周期
		_sm.set_session_expire_time(sp->ssid(), 30000);
		// 创建一个让客户端设置Cookie的响应
		string cookie_ssid = "SSID=" + to_string(sp->ssid());
		conn->append_header("Set-Cookie", cookie_ssid);
		return http_response(websocketpp::http::status_code::ok, true, "登录成功！", conn);
	}

	bool get_ssid_from_cookie(string cookie, const string key, string &ssid)
	{
		// Cookie的数据格式如下：Cookie: SSID=XXX; path=/;
		vector<string> sepcont1, sepcont2;
		Str_util ::sepstring(cookie, ", ", sepcont1);
		// 此时切割的格式就是SSID=XXX,再以=进行分隔符切割一次

		for (auto &e : sepcont1)
		{
			Str_util ::sepstring(e, "=", sepcont2);

			if (sepcont2.size() != 2)
				continue;
			else
			{
				if (sepcont2[0] == key)
				{
					ssid = sepcont2[1];
					return true;
				}
			}
		}
		return false;
	}

	// 用户信息获取时调用的函
	void information_handler(wsvr_t::connection_ptr conn)
	{

		websocketpp::http::parser::request req = conn->get_request();
		// 使用Cookie来获取对应
		// 先获取报头中的Cookie字段
		string cookie_str = conn->get_request_header("Cookie");
		if (cookie_str.empty())
		{
			DLOG("Cookie获取失败");
			// 为什么板书内部是true？
			return http_response(websocketpp::http::status_code::bad_request, false, "Cookie获取失败，请重新登录", conn);
		}

		// 获取Cookie成功后，我们需要分解切割Cookie字串，为什么？因为需要取出其中的SSID然后在Session管理器中查寻当前会话是否存在
		string ssid;
		if (get_ssid_from_cookie(cookie_str, "SSID", ssid) == false)
		{
			DLOG("SSID获取失败");
			return http_response(websocketpp::http::status_code::bad_request, false, "SSID获取失败，请重新登录", conn);
		}

		// 获取到SSID后，在SESSION管理器内部查找当前SSid是否有对应的会话对象
		seesion_ptr sp = _sm.get_session_by_ssid(stol(ssid));
		if (sp.get() == nullptr)
		{
			DLOG("对应SSID没有找到对应会话");
			return http_response(websocketpp::http::status_code::bad_request, false, "登录过期，请重新登录", conn);
		}

		// 找到了对应的会话后，返回对应的用户信息给客户端
		uint64_t user_id = sp->get_user();
		// bool select_by_id(uint64_t &id, Json::Value &user)
		Json::Value user_json;
		if (_ust.select_by_id(user_id, user_json) == false)
		{
			DLOG("数据库内没有找到对应用户");
			return http_response(websocketpp::http::status_code::bad_request, false, "用户不存在！请重新登录", conn);
		}

		string body;
		Json_util::serialize(user_json, body);
		conn->set_body(body);
		conn->append_header("Content-Type", "application/json");
		conn->set_status(websocketpp::http::status_code::ok);
		// 4. 刷新session的过期时间
		_sm.set_session_expire_time(sp->ssid(), SESSION_TIMEOUT);
		return;
	}

	// http请求回调函数
	void wshttp_handler(websocketpp::connection_hdl hdl)
	{
		// 获取对应请求报文的整体请求方法以及URI
		wsvr_t::connection_ptr conn = _wssver.get_con_from_hdl(hdl);
		// 拿取conn后，制作响应报文
		// 在测试中，使用一个登录界面进行响应，读取对应的文件内容放入正文后返回
		websocketpp::http::parser::request req = conn->get_request();
		string method = req.get_method();
		string uri = req.get_uri();
		if (method == "POST" && uri == "/reg")
		{
			return register_handler(conn);
		}
		else if (method == "POST" && uri == "/login")
		{
			return login_handler(conn);
		}
		else if (method == "GET" && uri == "/info")
		{
			return information_handler(conn);
		}
		else
		{
			return file_handler(conn);
		}
	}

	void ws_resp(wsserver_t::connection_ptr conn, Json::Value &resp)
	{
		std::string body;
		Json_util::serialize(resp, body);
		conn->send(body);
	}

	// 验证ssid函数，从cookie中获取session
	seesion_ptr get_ssid_from_cookie(wsvr_t::connection_ptr &conn)
	{
		Json::Value err_resp;
		websocketpp::http::parser::request req = conn->get_request();
		string cookie_str = conn->get_request_header("Cookie");
		if (cookie_str.empty())
		{
			DLOG("[大厅长连接]SSID获取失败");
			err_resp["optype"] = "hall_ready";
			err_resp["reason"] = "没有Cookie信息，需要重新登录";
			err_resp["result"] = false;
			ws_resp(conn, err_resp);
			return seesion_ptr();
		}
		string ssid;
		if (get_ssid_from_cookie(cookie_str, "SSID", ssid) == false)
		{
			DLOG("[大厅长连接]SSID获取失败");
			err_resp["optype"] = "hall_ready";
			err_resp["reason"] = "Cookie内部没有sessionID，需要重新登录";
			err_resp["result"] = false;
			ws_resp(conn, err_resp);
			return seesion_ptr();
		}

		seesion_ptr sp = _sm.get_session_by_ssid(stol(ssid));
		if (sp.get() == nullptr)
		{
			err_resp["optype"] = "hall_ready";
			err_resp["reason"] = "Session内部没有根据ssi找到对应session，需要重新登录";
			err_resp["result"] = false;
			ws_resp(conn, err_resp);
			return seesion_ptr();
		}

		return sp;
	}

	// 建立与大厅的长链接，先验证当前的客户端是否已经登录过，如果登录过了，那么就不能再登录了
	// 验证完毕后，将当前的用户加入到大厅的在线管理中
	// 加入后，将其会话改为永久存在直到用户下线
	void wsopen_hall(wsvr_t::connection_ptr conn)
	{
		Json::Value err_resp;
		websocketpp::http::parser::request req = conn->get_request();
		seesion_ptr sp = get_ssid_from_cookie(conn);
		if (sp.get() == nullptr)
			return;

		// 已获取到当前用户的session，将其加入到在线管理模块中，当然，在添加之前还需要验证是否已经存在
		if (_om.in_game_hall(sp->ssid()) == true || _om.in_game_room(sp->get_user()))
		{
			DLOG("该用户已登录过");
			err_resp["optype"] = "hall_ready";
			err_resp["reason"] = "该用户已登录过，无法再次登录";
			err_resp["result"] = false;
			return ws_resp(conn, err_resp);
		}

		// 添加入管理模块
		_om.enter_game_hall(sp->get_user(), conn);
		// 添加成功，返回一个报文，

		Json::Value success_resp;
		success_resp["optype"] = "hall_ready";
		success_resp["result"] = true;
		string body;
		Json_util::serialize(success_resp, body);
		conn->send(body);

		// 然后将会话生命周期设置为永久
		_sm.set_session_expire_time(sp->ssid(), SESSION_FOREVER);
	}

	// 从大厅中移除用户
	void wsclose_hall(wsserver_t::connection_ptr conn)
	{
		// 关闭链接，就是从大厅队列中移除当前用户，然后设置会话的生命周期为
		seesion_ptr sp = get_ssid_from_cookie(conn);
		if (sp.get() == nullptr)
			return;

		// 从在线管理模块大厅中移除当前用户
		_om.exit_game_hall(sp->get_user());
		// 2. 将session回复⽣命周期的管理，设置定时销毁
		_sm.set_session_expire_time(sp->ssid(), SESSION_TIMEOUT);
	}

	void wsopen_room(wsvr_t::connection_ptr &conn)
	{
		// 检查当前用户是否具有会话
		websocketpp::http::parser::request req = conn->get_request();
		seesion_ptr sp = get_ssid_from_cookie(conn);
		if (sp.get() == nullptr)
			return;

		Json::Value err_resp;

		// 2.检查是否重复登录
		if (_om.in_game_hall(sp->get_user()) ||
			_om.in_game_room(sp->get_user()))
		{
			err_resp["optype"] = "room_ready";
			err_resp["reason"] = "玩家重复登录！";
			err_resp["result"] = false;
			return ws_resp(conn, err_resp);
		}
		// 3. 获取客⼾端房间信息，因为此时两个用户已经被匹配管理模块放入到了一个房间内
		room_ptr rp = _rm.get_room_by_UserID(sp->get_user());
		if (rp.get() == nullptr)
		{
			DLOG("无法找到玩家的房间信息");
			err_resp["optype"] = "room_ready";
			err_resp["reason"] = "无法找到玩家的房间信息";
			err_resp["result"] = false;

			return ws_resp(conn, err_resp);
		}
		// 4.得到之后，将当前用户添加到房间中
		_om.enter_game_room(sp->get_user(), conn);

		// 5.返回一个报文
		Json::Value sucess_resp;

		sucess_resp["optype"] = "room_ready";
		sucess_resp["result"] = true;
		sucess_resp["room_id"] = (Json::UInt64)rp->id();
		sucess_resp["uid"] = (Json::UInt64)sp->get_user();
		sucess_resp["white_id"] = (Json::UInt64)rp->get_white_user();
		sucess_resp["black_id"] = (Json::UInt64)rp->get_black_user();
		ws_resp(conn, sucess_resp);

		// 设置生命周期
		return _sm.set_session_expire_time(sp->ssid(), SESSION_FOREVER);
	}

	// 关闭房间函数
	void wsclose_room(wsvr_t::connection_ptr &conn)
	{
		DLOG("有用户关闭了链接");
		seesion_ptr ssp = get_ssid_from_cookie(conn);
		if (ssp.get() == nullptr)
		{
			return;
		}
		// 1. 将玩家从在线⽤⼾管理中移除
		_om.exit_game_room(ssp->get_user());
		// 2. 将session回复⽣命周期的管理，设置定时销毁
		_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);
		// 3. 将玩家从游戏房间中移除，房间中所有⽤⼾退出了就会销毁房间
		_rm.remove_room_user(ssp->get_user());
	}

	// 握手成功处理函数
	// 调用这个函数时，客户端和服务器相当于需要建立长链接，但是不能简单的直接建立，还需要过一次会话是否存在的检验
	// 但是建立长链接的场景有两种，一种是大厅的长连接，另一种是房间的长链接，需要区分这两个长链接来进行建立和检验
	void wsopen_handler(websocketpp::connection_hdl hdl)
	{
		wsvr_t::connection_ptr conn = _wssver.get_con_from_hdl(hdl);
		websocketpp::http::parser::request req = conn->get_request();

		string uri = req.get_uri();

		if (uri == "/hall")
		{
			return wsopen_hall(conn);
		}
		else if (uri == "/room")
		{
			return wsopen_room(conn);
		}
	}
	// 断开处理函数
	void wsclose_handler(websocketpp::connection_hdl hdl)
	{
		// 当链接断开的时候，删除掉对应在线管理模块内部的客户端信息
		// 还是先获取当前的会话
		wsvr_t::connection_ptr conn = _wssver.get_con_from_hdl(hdl);
		websocketpp::http::parser::request req = conn->get_request();

		string uri = req.get_uri();
		if (uri == "/hall")
		{
			return wsclose_hall(conn);
		}
		else if (uri == "/room")
		{
			return wsclose_room(conn);
		}
	}

	// typedef websocketpp :: server<websocketpp::config::asio>
	// wsvr_t::message_ptr mp;
	// mp->get_payload();
	// 处理客户端在建立长连接之后发上来的各种信息与请求报文

	void wsmessage__hall(websocketpp::connection_hdl hdl, wsvr_t::message_ptr mp)
	{
		// 验证客户端
		wsvr_t::connection_ptr conn = _wssver.get_con_from_hdl(hdl);

		websocketpp::http::parser::request req = conn->get_request();
		seesion_ptr sp = get_ssid_from_cookie(conn);
		if (sp.get() == nullptr)
			return;

		// 客户端验证成功，将报文反序列化
		Json::Value req_json;
		// 这里不能使用getbody是因为当前已经是websocket协议状态通信了，getbody只能获取http的正文
		// string body = req.get_body();
		std::string req_body = mp->get_payload();
		bool ret = Json_util ::unserialize(req_body, req_json);
		if (ret == false)
		{
			ELOG("客户端消息反序列化失败！");
			Json::Value err_resp;
			err_resp["result"] = false;
			err_resp["reason"] = "反序列化失败";
			return ws_resp(conn, err_resp);
		}

		// 反序列化成功，校验当前客户端需求说明服务，也就是比对optye
		// 如果当前发送的是匹配开始的请求
		if (req_json["optype"].isNull() == false && req_json["optype"].asString() == "match_start")
		{
			// 将当前的用户设置进匹配队列
			_mc.add(sp->get_user());
			Json::Value resp;
			resp["optype"] = "match_start";
			resp["reason"] = "成功进入匹配队列……";
			resp["result"] = true;
			return ws_resp(conn, resp);
		}
		// 当操作请求为停止匹配时
		else if (req_json["optype"].isNull() == false && req_json["optype"].asString() == "match_stop")
		{
			// 将当前的用户设置进匹配队列
			_mc.del(sp->get_user());
			Json::Value resp;
			resp["optype"] = "match_stop";
			resp["reason"] = "已停止匹配";
			resp["result"] = true;
			return ws_resp(conn, resp);
		}

		Json::Value resp_json;
		resp_json["optype"] = "unknow";
		resp_json["reason"] = "请求类型未知";
		resp_json["result"] = false;
		return ws_resp(conn, resp_json);
	}

	void wsmessage__room(websocketpp::connection_hdl hdl, wsvr_t::message_ptr mp)
	{
		wsvr_t::connection_ptr conn = _wssver.get_con_from_hdl(hdl);

		// 验证session
		websocketpp::http::parser::request req = conn->get_request();
		seesion_ptr sp = get_ssid_from_cookie(conn);
		if (sp.get() == nullptr)
			return;

		// 获取当前发起请求的客户端所在的房间
		room_ptr rp = _rm.get_room_by_UserID(sp->get_user());
		if(rp.get() == nullptr)
		{
			Json::Value err_resp;
			err_resp["optype"] ="unknow";
			err_resp["reason"] = "请求类型未知";
			err_resp["result"] = false; 
			return ws_resp(conn, err_resp);
		}
 
		// 获取报文，然后将其反序列化
		string body;
		Json::Value req_json; 
		body = mp->get_payload();
		bool ret = Json_util :: unserialize(body,req_json);

		DLOG("当前房间收到的optype为:%s",req_json["optype"].asCString());
		DLOG("当前房间收到报文为:%s",body.c_str());

		if(ret == false)
		{
			Json::Value err_resp;
			err_resp["optype"] ="unknow";
			err_resp["reason"] = "房间信息反序列化失败！";
			err_resp["result"] = false; 
			return ws_resp(conn, err_resp);
		}

		return rp->handle_req(req_json);
	}

	void wsmessage__handler(websocketpp::connection_hdl hdl, wsvr_t::message_ptr mp)
	{
		wsserver_t::connection_ptr conn = _wssver.get_con_from_hdl(hdl);
		websocketpp::http::parser::request req = conn->get_request();
		std::string uri = req.get_uri();
		if (uri == "/hall")
		{
			return wsmessage__hall(conn, mp);
		}
		else if (uri == "/room")
		{
			return wsmessage__room(conn, mp);
		}
	}

public:
	// 构造函数，初始化所有的模块，其中初始化的顺序还需要注意以下
	gobang_server(
		const string &hostname,
		const string &username,
		const string &password,
		const string &dbname,
		uint16_t port = 3306)
		: _ust(hostname, username, password, dbname, port),
		  _webroot(WEBROOT), _rm(&_ust, &_om), _sm(&_wssver), _mc(&_om, &_ust, &_rm)
	{

		_wssver.set_access_channels(websocketpp::log::alevel::none);
		_wssver.init_asio();
		_wssver.set_reuse_addr(true);
		_wssver.set_open_handler(bind(&gobang_server::wsopen_handler, this, std::placeholders::_1));							   /*websocket握⼿成功回调处理函数*/
		_wssver.set_close_handler(bind(&gobang_server::wsclose_handler, this, std::placeholders::_1));							   /*websocket连接关闭回调处理函数*/
		_wssver.set_message_handler(bind(&gobang_server::wsmessage__handler, this, std::placeholders::_1, std::placeholders::_2)); /*websocket消息回调处理函数*/
		_wssver.set_http_handler(bind(&gobang_server::wshttp_handler, this, std::placeholders::_1));							   /*http请求回调处理函数*/
	}

	void start(int port)
	{
		_wssver.listen(port);
		_wssver.start_accept();
		_wssver.run();
	}
};